/*
 * Created by Jaren at 2021/8/21 15:24
 */
import 'dart:typed_data';

import 'package:binary/binary.dart';
import 'package:inmotion_protocol/base/base_factory.dart';
import 'package:inmotion_protocol/common/product_model.dart';
import 'package:inmotion_protocol/common/vehicle_info.dart';
import 'package:inmotion_protocol/ezcan/bean/ebike/ebike_settings.dart';
import 'package:inmotion_protocol/ezcan/ezcan_constant.dart';
import 'package:inmotion_protocol/ezcan/ezcan_message.dart';
import 'package:inmotion_protocol/util/byte_util.dart';

class EzcanFactory extends MsgFactory<EzcanMessage> {
  ModelCompact? _compact;

  @override
  void init(ProductProperty productProperty) {
    model = productProperty.model;
    version = productProperty.versionWrapper;
    if (!model.isEzCANModel())
      throw Exception("Invalid model [$model] for EzCAN.");
    if (model.isL8Series()) {
      _compact = L8Compact(model);
    } else if (model.isR1Series()) {
      _compact = R1Compact(model);
    } else {
      _compact = DefaultCompact(model);
    }
  }

  static EzcanMessage genMsg(int id, Uint8ClampedList data, int length,
      int channel, int format, int type,
      [Uint8ClampedList? extra, int? check]) {
    return EzcanMessage(
        Uint32(id),
        data,
        ByteUtil.toUint8(length),
        ByteUtil.toUint8(channel),
        ByteUtil.toUint8(format),
        ByteUtil.toUint8(type),
        extra,
        check == null ? null : ByteUtil.toUint8(check));
  }

  static EzcanMessage genLocalMsg(
      int id, Uint8ClampedList data, int length, int format, int type,
      [Uint8ClampedList? extra]) {
    return genMsg(
        id, data, length, EzcanConstant.CHANNEL_LOCAL, format, type, extra);
  }

  static EzcanMessage genLocalStandardMsg(
      int id, Uint8ClampedList data, int length, int type,
      [Uint8ClampedList? extra]) {
    return genLocalMsg(
        id, data, length, EzcanConstant.FORMAT_STANDARD, type, extra);
  }

  static EzcanMessage genLocalExtraMsg(
      int id, Uint8ClampedList data, int length, int type,
      [Uint8ClampedList? extra]) {
    return genLocalMsg(
        id, data, length, EzcanConstant.FORMAT_EXTRA, type, extra);
  }

  static EzcanMessage genLocalStandardReadMsg(
      int id, Uint8ClampedList data, int length,
      [Uint8ClampedList? extra]) {
    return genLocalMsg(id, data, length, EzcanConstant.FORMAT_STANDARD,
        EzcanConstant.TYPE_READ, extra);
  }

  static EzcanMessage genLocalStandardWriteMsg(
      int id, Uint8ClampedList data, int length,
      [Uint8ClampedList? extra]) {
    return genLocalMsg(id, data, length, EzcanConstant.FORMAT_STANDARD,
        EzcanConstant.TYPE_WRITE, extra);
  }

  static EzcanMessage genLocalExtraReadMsg(
      int id, Uint8ClampedList data, int length,
      [Uint8ClampedList? extra]) {
    return genLocalMsg(id, data, length, EzcanConstant.FORMAT_EXTRA,
        EzcanConstant.TYPE_READ, extra);
  }

  static EzcanMessage genLocalExtraWriteMsg(
      int id, Uint8ClampedList data, int length,
      [Uint8ClampedList? extra]) {
    return genLocalMsg(id, data, length, EzcanConstant.FORMAT_EXTRA,
        EzcanConstant.TYPE_WRITE, extra);
  }

  static EzcanMessage genRequestErrorCodeMsg() {
    return genLocalStandardReadMsg(EzcanConstant.ID_LAST_EVENT,
        Uint8ClampedList.fromList([0, 0, 0, 0, 0, 0, 0, 0]), 8);
  }

  static EzcanMessage genSettingsMsg(
      int settingsId, Uint8ClampedList settingsData) {
    settingsData.insertAll(0, [0, 0, 0, 0]);
    return genLocalStandardWriteMsg(
        settingsId, settingsData, settingsData.length, null);
  }

  static EzcanMessage genQuickConnectAckMsg() {
    return genLocalStandardWriteMsg(EzcanConstant.ID_QUICK_CONNECT_RSP,
        Uint8ClampedList.fromList([1, 0, 0, 0, 0, 0, 0, 0]), 8);
  }

  static EzcanMessage genVerifyPwdMsg(String pwd) {
    Uint8ClampedList data = Uint8ClampedList(8);
    for (var i = 0; i < pwd.length; i++) {
      data[i] = 0x30 + (int.tryParse(pwd[i]) ?? 0);
    }
    return genLocalStandardWriteMsg(EzcanConstant.ID_VERIFY_PWD, data, 8);
  }

  static EzcanMessage genResetPwdPwdMsg(String pwd) {
    Uint8ClampedList data = Uint8ClampedList(8);
    for (var i = 0; i < pwd.length; i++) {
      data[i] = 0x30 + (int.tryParse(pwd[i]) ?? 0);
    }
    return genLocalStandardWriteMsg(EzcanConstant.ID_CHANGE_PWD, data, 8);
  }

  static EzcanMessage genRequestSlowInfoMsg() {
    return genLocalStandardReadMsg(
        EzcanConstant.ID_SLOW_DATA,
        Uint8ClampedList.fromList(
            [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]),
        8);
  }

  static EzcanMessage genRequestFastInfoMsg() {
    return genLocalStandardWriteMsg(
        EzcanConstant.ID_FAST_DATA,
        Uint8ClampedList.fromList(
            [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]),
        8);
  }

  static EzcanMessage genRequestSoundPackIdMsg() {
    return genLocalStandardReadMsg(
        EzcanConstant.idSoundPackId, ByteUtil.num2BytesLe(0, 8), 8);
  }

  static EzcanMessage genSetModeMsg(int value) {
    Uint8ClampedList bytes = Uint8ClampedList(8);
    ByteUtil.num2BytesLe(0xB2, 4, bytes);
    ByteUtil.num2BytesLe(value, 4, bytes, 4);
    return genLocalStandardWriteMsg(EzcanConstant.ID_REMOTE_CTRL, bytes, 8);
  }

  static EzcanMessage genSetSettingsMsg(int id, int data) {
    Uint8ClampedList bytes = Uint8ClampedList(8);
    ByteUtil.num2BytesLe(id, 4, bytes);
    ByteUtil.num2BytesLe(data, 4, bytes, 4);
    return genLocalStandardWriteMsg(EzcanConstant.ID_SETTINGS, bytes, 8);
  }

  static EzcanMessage genCtrlMsg(int id, int data) {
    Uint8ClampedList bytes = Uint8ClampedList(8);
    ByteUtil.num2BytesLe(data, 4, bytes);
    ByteUtil.num2BytesLe(0, 4, bytes, 4);
    return genLocalStandardWriteMsg(id, bytes, 8);
  }

  static EzcanMessage genSyncRTCMsg(DateTime dateTime) {
    Uint8ClampedList bytes = Uint8ClampedList(8);
    bytes[0] = 1;
    bytes[1] = dateTime.second;
    bytes[2] = dateTime.minute;
    bytes[3] = dateTime.hour;
    bytes[4] = dateTime.day;
    bytes[5] = dateTime.month;
    ByteUtil.num2BytesLe(dateTime.year, 2, bytes, 6);
    return genLocalStandardWriteMsg(EzcanConstant.idSyncRTC, bytes, 8);
  }

  static EzcanMessage genOpenFileMsg(int type, int fileTag) {
    Uint8ClampedList bytes = Uint8ClampedList(8);
    ByteUtil.num2BytesLe(type, 4, bytes);
    bytes[4] = fileTag;
    return genLocalStandardWriteMsg(EzcanConstant.idOpenFile, bytes, 8);
  }

  static EzcanMessage genSetFileOffsetMsg(int type, int offset) {
    Uint8ClampedList bytes = Uint8ClampedList(8);
    ByteUtil.num2BytesLe(offset, 4, bytes, 0);
    ByteUtil.num2BytesLe(type, 4, bytes, 4);
    return genMsg(EzcanConstant.idSetFileOffset, bytes, 8, 0,
        EzcanConstant.FORMAT_STANDARD, EzcanConstant.TYPE_WRITE);
  }

  static EzcanMessage genWriteFileMsg(Uint8ClampedList content) {
    Uint8ClampedList data = Uint8ClampedList(8);
    ByteUtil.num2BytesLe(content.length, 4, data);

    return genMsg(
        EzcanConstant.idWriteFile,
        data,
        EzcanConstant.LENGTH_FLAG_EXTRA,
        0,
        EzcanConstant.FORMAT_EXTRA,
        EzcanConstant.TYPE_WRITE,
        content);
  }

  static EzcanMessage genCloseFileMsg() {
    return genMsg(EzcanConstant.idCloseFile, Uint8ClampedList(8), 8, 0,
        EzcanConstant.FORMAT_STANDARD, EzcanConstant.TYPE_WRITE);
  }

  static EzcanMessage genGetLogFileInfoMsg(int logType, int logId) {
    Uint8ClampedList data = Uint8ClampedList(8);
    ByteUtil.num2BytesLe(logType, 4, data, 0);
    ByteUtil.num2BytesLe(logId, 4, data, 4);

    return genLocalStandardWriteMsg(EzcanConstant.idGetLogFileInfo, data, 8);
  }

  static EzcanMessage genOpenLogFileInfoMsg(int logType, int logId) {
    Uint8ClampedList data = Uint8ClampedList(8);
    ByteUtil.num2BytesLe(logType, 4, data, 0);
    ByteUtil.num2BytesLe(0, 4, data, 4);
    return genLocalStandardWriteMsg(EzcanConstant.idOpenLogFile, data, 8);
  }

  static EzcanMessage genGetLogFileContentMsg(int offset, int size) {
    Uint8ClampedList data = Uint8ClampedList(8);
    ByteUtil.num2BytesLe(offset, 4, data, 0);
    ByteUtil.num2BytesLe(size, 4, data, 4);
    return genLocalStandardWriteMsg(EzcanConstant.idGetLogFileContent, data, 8);
  }

  static EzcanMessage genCloseLogFileInfoMsg(int logType) {
    Uint8ClampedList data = Uint8ClampedList(8);
    ByteUtil.num2BytesLe(logType, 4, data, 0);
    ByteUtil.num2BytesLe(0, 4, data, 4);
    return genLocalStandardWriteMsg(EzcanConstant.idCloseLogFile, data, 8);
  }

  static EzcanMessage genDeleteLogFileInfoMsg(int logType, int logId) {
    Uint8ClampedList data = Uint8ClampedList(8);
    ByteUtil.num2BytesLe(logType, 4, data, 0);
    ByteUtil.num2BytesLe(logId, 4, data, 4);
    return genLocalStandardWriteMsg(EzcanConstant.idDeleteLogFile, data, 8);
  }

  EzcanMessage genRequestModelMsg() {
    return genRequestSlowInfoMsg();
  }

  EzcanMessage genRequestSettingsMsg() {
    return genRequestSlowInfoMsg();
  }

  EzcanMessage genRequestAssistantSettingsMsg() {
    return genLocalStandardReadMsg(
        EzcanConstant.ID_ASSISTANCE_SETTINGS, Uint8ClampedList(8), 8);
  }

  EzcanMessage genSetAssistantSettingsMsg(
      AssistanceSettings assistanceSettings) {
    Uint8ClampedList data = Uint8ClampedList(8);
    data[0] = assistanceSettings.assistanceForce.value;
    data[1] = assistanceSettings.assistanceStartupSensitivity.value;
    data[2] = assistanceSettings.assistanceStartupForce.value;
    data[3] = assistanceSettings.assistanceStartupSpeed.value;
    data[4] = assistanceSettings.acceleratorStartupForce.value;
    data[5] = assistanceSettings.maxPowerLimit.value;
    data[6] = assistanceSettings.ridingMode.value;
    data[7] = assistanceSettings.limitSpeed.value;

    return genLocalStandardWriteMsg(
        EzcanConstant.ID_ASSISTANCE_SETTINGS, data, 8);
  }

  EzcanMessage genRequestSnMsg() {
    return genRequestSlowInfoMsg();
  }

  EzcanMessage genCalibrateAngleZMsg() {
    throw UnimplementedError();
  }

  static EzcanMessage genRequestLightEffectIdMsg() {
    return genLocalStandardReadMsg(
        EzcanConstant.idLightEffectId, Uint8ClampedList(8), 8);
  }

  EzcanMessage genRequestLightEffectIdListMsg() {
    return genLocalStandardReadMsg(
        EzcanConstant.idLightEffects, Uint8ClampedList(8), 8);
  }

  EzcanMessage genRequestLightEffectColorMsg(int style) {
    Uint8ClampedList bytes = Uint8ClampedList(8);
    ByteUtil.num2BytesLe(0x1, 4, bytes);
    ByteUtil.num2BytesLe(0, 4, bytes, 4);
    Uint8ClampedList extra = Uint8ClampedList(1);
    extra[0] = style;
    return genLocalExtraWriteMsg(EzcanConstant.idLightEffectColor, bytes,
        EzcanConstant.LENGTH_FLAG_EXTRA, extra);
  }

  EzcanMessage genCalibrateMsg() {
    Uint8ClampedList bytes = Uint8ClampedList(8);
    ByteUtil.num2BytesLe(0x98765432, 4, bytes);
    ByteUtil.num2BytesLe(0, 4, bytes, 4);
    return genLocalStandardWriteMsg(EzcanConstant.ctrlIdCalibration, bytes, 8);
  }

  EzcanMessage genCruiseOnOffMsg(bool on) {
    return genSetModeMsg(
        on ? EzcanConstant.ctrlModeCruiseOn : EzcanConstant.ctrlModeCruiseOff);
  }

  EzcanMessage genGpsOnOffMsg(bool on) {
    return genCtrlMsg(EzcanConstant.ID_GPS_STATE, on ? 1 : 0);
  }

  EzcanMessage genSetLanguageMsg(int language) {
    return genCtrlMsg(EzcanConstant.ID_LANGUAGE, language);
  }

  EzcanMessage genSetTurningSensitivity(int sensitivity) {
    return genSetSettingsMsg(
        EzcanConstant.SETTINGS_ID_STEERING_SENSITIVITY, sensitivity);
  }

  EzcanMessage genHeadlightOnOffMsg(
    bool? on, {
    bool? lowBeamOn,
    bool? highBeamOn,
  }) {
    if (on == null) throw Exception('Param [on] should be set.');
    return genCtrlMsg(EzcanConstant.ID_LIGHT, on ? 1 : 0);
  }

  EzcanMessage genLockMsg(bool on) {
    return genSetModeMsg(
        on ? EzcanConstant.ctrlModelLock : EzcanConstant.ctrlModelUnlock);
  }

  EzcanMessage genPlaySoundItemMsg(int index) {
    Uint8ClampedList bytes = Uint8ClampedList(8);
    ByteUtil.num2BytesLe(index, 4, bytes);
    return genLocalStandardWriteMsg(EzcanConstant.idPlayMusic, bytes, 8);
  }

  EzcanMessage genSetDriveModeMsg(int driveMode) {
    return genSetSettingsMsg(EzcanConstant.settingsIdDriveMode, driveMode);
  }

  EzcanMessage genSetLightEffectIdMsg(int id) {
    Uint8ClampedList data = Uint8ClampedList(8);
    ByteUtil.num2BytesLe(id, 4, data, 0);

    return genMsg(
        EzcanConstant.idLightEffectId,
        data,
        data.length,
        0, // remote
        EzcanConstant.FORMAT_STANDARD,
        EzcanConstant.TYPE_WRITE);
  }

  EzcanMessage genLightEffectOnOffMsg([bool on = true]) {
    return genSetModeMsg(on
        ? EzcanConstant.ctrlModeLightEffectOn
        : EzcanConstant.ctrlModeLightEffectOff);
  }

  EzcanMessage genSetLightEffectColorMsg(int style, int color) {
    Uint8ClampedList extra = Uint8ClampedList(2 + 3);
    extra[0] = style | 0x80;
    extra[1] = 1;
    ByteUtil.num2Bytes(color, 3, extra, 2);
    Uint8ClampedList data = Uint8ClampedList(8);
    ByteUtil.num2Bytes(extra.length, 4, data);
    return genLocalExtraWriteMsg(
        EzcanConstant.idLightEffectColor, data, 8, extra);
  }

  EzcanMessage genSetPedalSensitivityMsg(int sensitivity, [int? sensitivity2]) {
    return genSetSettingsMsg(EzcanConstant.SETTINGS_ID_PEDAL_SENSITIVITY,
        sensitivity * 2048 ~/ 100 + 2048);
  }

  EzcanMessage genSetPitchAngleZeroMsg(int angle) {
    return genSetSettingsMsg(EzcanConstant.SETTINGS_ID_ANGLE_Z, angle);
  }

  EzcanMessage genSetSpeedLimitMsg(int speedLimit) {
    speedLimit = speedLimit ~/ 100 * _compact!.speedUnit();
    return genSetSettingsMsg(EzcanConstant.SETTINGS_ID_MAX_SPEED, speedLimit);
  }

  EzcanMessage genSetStandbyTimeMsg(int seconds) {
    return genSetSettingsMsg(
        EzcanConstant.SETTINGS_ID_STANDBY_TIME, seconds ~/ 60);
  }

  EzcanMessage genSetVolumeMsg(int volume) {
    return genCtrlMsg(EzcanConstant.ID_VOLUME, volume);
  }

  EzcanMessage genRequestVolumeMsg() {
    Uint8ClampedList bytes = Uint8ClampedList(8);
    ByteUtil.num2BytesLe(0, 8, bytes);
    return genLocalStandardReadMsg(EzcanConstant.ID_VOLUME, bytes, 8);
  }

  EzcanMessage genSoundPackOnOffMsg([bool on = true]) {
    Uint8ClampedList bytes = Uint8ClampedList(8);
    ByteUtil.num2BytesLe(on ? 1 : 0, 4, bytes);
    return genLocalStandardWriteMsg(EzcanConstant.idSoundPackState, bytes, 8);
  }

  EzcanMessage genSpinKillOnOffMsg([bool on = true]) {
    return genCtrlMsg(EzcanConstant.ID_HANDLE, on ? 1 : 0);
  }

  EzcanMessage genRequestSpinKillStateMsg() {
    Uint8ClampedList bytes = Uint8ClampedList(8);
    ByteUtil.num2BytesLe(0, 8, bytes);
    return genLocalStandardReadMsg(EzcanConstant.ID_HANDLE, bytes, 8);
  }

  EzcanMessage genTransportationModeOnOffMsg([bool on = true]) {
    return genSetModeMsg(on
        ? EzcanConstant.CTRL_MODE_TRANSPORT_ON
        : EzcanConstant.CTRL_MODE_TRANSPORT_OFF);
  }

  EzcanMessage genEraseDataMsg() {
    Uint8ClampedList bytes = Uint8ClampedList(8);
    ByteUtil.num2BytesLe(1, 4, bytes);
    return genLocalStandardWriteMsg(EzcanConstant.CTRL_ID_WIPE_DATA, bytes, 8);
  }

  EzcanMessage genPatchL8LockBugMsg(bool lock) {
    if (lock) {
      Uint8ClampedList extra = Uint8ClampedList(1 + 1 + 11 * 2);
      // 通道号    bit0  代表油门    bit1  代表刹车，特别地  bit7  为1代表重置      这里仅选择油门
      extra[0] = 0x01;
      // 采样点个数  11  个
      extra[1] = 0x0B;
      // 采样点值
      Uint8ClampedList sample = Uint8ClampedList(2);
      // 11个采样点均配置为饱和值  0x7FF
      ByteUtil.num2BytesLe(0x7FF, 2, sample, 0);

      for (int i = 2, length = extra.length; i < length; i += 2) {
        ByteUtil.copy(sample, 0, extra, i, 2);
      }
      Uint8ClampedList data = Uint8ClampedList(8);
      ByteUtil.num2BytesLe(4, 4, data);
      return genLocalExtraWriteMsg(EzcanConstant.idL8AccParams, data, 8, extra);
    } else {
      Uint8ClampedList data = Uint8ClampedList(8);
      // 通道号 bit7  为1表示将所有采样点恢复默认值
      data[0] = 0x80;
      return genLocalStandardWriteMsg(EzcanConstant.idL8AccParams, data, 8);
    }
  }
}

abstract class ModelCompact {
  ProductModel model;

  ModelCompact(this.model);

  int speedUnit() {
    return 1000;
  }
}

class DefaultCompact extends ModelCompact {
  DefaultCompact(ProductModel model) : super(model);
}

class L8Compact extends ModelCompact {
  L8Compact(ProductModel model) : super(model);

  @override
  int speedUnit() {
    return 3812;
  }
}

class R1Compact extends ModelCompact {
  R1Compact(ProductModel model) : super(model);

  @override
  int speedUnit() {
    return 3812;
  }
}
